package com.doge.walknovel.business.home.discover

import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import com.doge.walknovel.api.*
import com.doge.walknovel.api.model.BannerInfo
import com.doge.walknovel.api.model.NovelInfo
import com.doge.walknovel.api.model.StrategyInfo
import com.doge.walknovel.business.home.discover.DiscoverState.Companion.ID_BANNER
import com.doge.walknovel.business.home.discover.DiscoverState.Companion.ID_GRID
import com.doge.walknovel.business.home.discover.DiscoverState.Companion.ID_GRID_RANK
import com.doge.walknovel.business.home.discover.DiscoverState.Companion.ID_LINEAR
import com.doge.walknovel.business.home.discover.DiscoverState.Companion.ID_STRATEGY_TAB
import com.doge.walknovel.core.base.LoadStatus
import com.doge.walknovel.core.base.adapters.*
import com.doge.walknovel.utils.*
import com.google.gson.reflect.TypeToken

class DiscoverRepository {
    private val folder = "ad"
    private val key = "data"

    val loading = MutableLiveData<LoadStatus>()
    private var state: DiscoverState = DiscoverState()
    private var dataSource: XPagedKeyDataSource? = null

    fun request(justCache: Boolean) {
        runOnIO {
            if (state.strategyList.isNullOrEmpty()) {
                val disk: String? = DiscoverCacheHelper.loadCache()
                val cache = fromJson<List<StrategyInfo>>(
                    disk, object : TypeToken<List<StrategyInfo>>() {}.type
                )
                runOnUI { if (cache.isNullOrEmpty()) loading.value = LoadStatus.LOADING }
                runOnWork { if (cache?.isNotEmpty() == true) reduceOnNext(cache) }
            }

            if (justCache) return@runOnIO

            api.novelHomePage()
                .map(ApiResultMapTransformer())
                .observeOnWork()
                .subscribe(object : ObserverAdapter<List<StrategyInfo>>() {
                    override fun onNext(t: List<StrategyInfo>) {
                        reduceOnNext(t)
                        DiscoverCacheHelper.saveCache(t)
                    }

                    override fun onError(e: Throwable) {
                        super.onError(e)
                        reduceOnError()
                    }
                })
        }
    }

    private fun reduceOnNext(data: List<StrategyInfo>) {
        require(!isMainThread())

        val bookList = mutableListOf<NovelInfo>()
        val list = mutableListOf<String>()

        var banners = when {
            state.banners.isNullOrEmpty() -> {
                val disk: String? = spReadString(key, null, folder)
                fromJson<List<BannerInfo>>(disk, object : TypeToken<List<BannerInfo>>() {}.type)
            }
            else -> state.banners
        }
        banners = banners?.filter { it.isActivating }
        if (banners?.isNotEmpty() == true) {
            list.add(ID_BANNER)
        }

        data.forEach {
            list.add("$ID_STRATEGY_TAB${it.titleId}")
            if (it.showType == StrategyInfo.VIEW_TYPE_GRID) {
                it.books?.forEach { book ->
                    list.add("${if (it.showRank == 1) ID_GRID_RANK else ID_GRID}${book.novelId}")
                }
            } else if (it.showType == StrategyInfo.VIEW_TYPE_LINEAR) {
                it.books?.forEach { book ->
                    list.add("$ID_LINEAR${book.novelId}")
                }
            }
            it.books?.forEach { novel -> novel.appReferId = it.titleId }
            bookList.addAll(it.books ?: listOf())
        }
        state = state.copy(
            list = list,
            banners = banners ?: listOf(),
            strategyList = data.map { it.titleId.toString() },
            strategyInfos = mapOf<Int, StrategyInfo>().appendData(data) { it.titleId },
            books = mapOf<Int, NovelInfo>().appendData(bookList) { it.novelId }
        )
        dataSource?.invalidate()
        runOnUI { loading.value = LoadStatus.SUCCESS }
    }

    private fun reduceOnError() {
        require(!isMainThread())
        runOnUI { loading.value = LoadStatus.FAILURE }
    }

    fun requestAd() {
        api
            .adList(1)
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<List<BannerInfo>>() {
                override fun onNext(t: List<BannerInfo>) {
                    val data = t.filter { it.isActivating }
                    reduceBanner(data)
                    spWrite(key, toJson(data), folder)
                }

                override fun onError(e: Throwable) {
                    // silently
                }
            })
    }

    private fun reduceBanner(t: List<BannerInfo>) {
        require(!isMainThread())
        state = state.copy(
            list = state.list.insertOrReplace(ID_BANNER, 0),
            banners = t,
        )
        log("discover invalidate banner")
        dataSource?.invalidate()
    }

    fun dataSource(): DataSource.Factory<Int, Cell> =
        object : DataSource.Factory<Int, Cell>() {
            override fun create(): DataSource<Int, Cell> {
                return object : XPagedKeyDataSource() {
                    override fun totalCount(): Int {
                        return state.list.size
                    }

                    override fun loadRange(start: Int, count: Int): List<Cell?> {
                        return state.list.mapToCell(start, count) {
                            when {
                                it == ID_BANNER -> Cell(ItemType.Banner, it, extra = state.banners)
                                it.startsWith(ID_STRATEGY_TAB) -> Cell(
                                    ItemType.Strategy,
                                    it.substring(ID_STRATEGY_TAB.length),
                                    extra = state.strategyInfos[it.substring(ID_STRATEGY_TAB.length)
                                        .toInt()]
                                )
                                it.startsWith(ID_GRID_RANK) -> Cell(
                                    ItemType.GridRankContent,
                                    it.substring(ID_GRID_RANK.length),
                                    extra = state.books[it.substring(ID_GRID_RANK.length).toInt()]
                                )
                                it.startsWith(ID_GRID) -> Cell(
                                    ItemType.GridContent,
                                    it.substring(ID_GRID.length),
                                    extra = state.books[it.substring(ID_GRID.length).toInt()]
                                )
                                it.startsWith(ID_LINEAR) -> Cell(
                                    ItemType.LinearContent,
                                    it.substring(ID_LINEAR.length),
                                    extra = state.books[it.substring(ID_LINEAR.length).toInt()]
                                )
                                else -> Cell(
                                    com.doge.walknovel.core.base.adapters.ItemType.Unknown,
                                    ""
                                )
                            }
                        }
                    }
                }.also { dataSource = it }
            }
        }

    companion object {
        private var INS: DiscoverRepository? = null

        fun get(): DiscoverRepository {
            return INS ?: synchronized(this) {
                INS ?: DiscoverRepository().also { INS = it }
            }
        }
    }
}